package controller;

import controller.commands.AddConnectionCommand;
import controller.commands.BasicCommand;
import controller.commands.ContainerCommand;
import controller.commands.RedrawCommand;
import load.parent.Store;
import core.BoxNode;
import core.Node;
import core.OutPort;
import core.ValueTransmitterNode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import model.graphic.locations.AbsoluteLocation;
import model.graphic.locations.Location;
import model.graphic.objects.AbstractHandle;
import model.graphic.objects.AbstractInPort;
import model.graphic.objects.AbstractOutPort;
import model.graphic.objects.GBoxNode;
import model.graphic.objects.Attachment;
import model.graphic.objects.GConnection;
import model.graphic.objects.GDispatcher;
import model.graphic.objects.GDispatcherPort;
import model.graphic.objects.GHandleConnection;
import model.graphic.objects.GInput;
import model.graphic.objects.GInputPort;
import model.graphic.objects.GNode;
import model.graphic.objects.GOutput;
import model.graphic.objects.GOutputPort;
import model.graphic.objects.GResizeHandle;
import model.graphic.objects.GTransmitterNode;
import model.graphic.objects.GTrigger;
import model.graphic.objects.GTriggerConnection;
import model.graphic.objects.GTriggerPort;
import model.graphic.objects.Graphical;
import model.graphic.objects.RightRectangleHandle;

/**
 *
 * @author Jelle, seriously modified by Ben
 */
public class PrototypeFactory {

    private GBoxNode prototypeGBoxNode;
    private BidirectionalHashMap sync = BidirectionalHashMap.getInstance();
    private GBoxNode gBoxClone;
    private BoxNode cBoxClone,  prototypeCBoxNode;
    private HashMap<String, Node> nodes = new HashMap<String, Node>();
    private HashMap<String, GTransmitterNode> transmitterNodes = new HashMap<String, GTransmitterNode>();
    private Location location;
    private ArrayList<BasicCommand> comms = new ArrayList<BasicCommand>();

    public PrototypeFactory(GBoxNode boxNode) {
        this.prototypeGBoxNode = boxNode;
    }

    public PrototypeFactory(Location location, GBoxNode gBoxNode, BoxNode cBoxNode) {
        this.location = location;
        this.prototypeGBoxNode = gBoxNode;
        this.prototypeCBoxNode = cBoxNode;
        makeBoxNode();
    }

    private void makeBoxNode() {
        //Codebehind BoxNode clonen

        prototypeCBoxNode = (BoxNode) sync.get(prototypeGBoxNode);
        gBoxClone = (GBoxNode) prototypeGBoxNode.clone();
        cBoxClone = new BoxNode();
        Location locationold = gBoxClone.getGraphLocation();
        gBoxClone.setLocations(location);

        ArrayList<Graphical> replace = new ArrayList<Graphical>();//Will contain the new internal elements
        ArrayList<Graphical> replaceTransmitters = new ArrayList<Graphical>();//Will contain the new transmitters
        ArrayList<Graphical> toClone = gBoxClone.getGraphicModel().getAllObjectNoInternals();//gBoxClone.getInternalObjects();
        //Why is toClone2 needed? All it does is removing it's elements
        ArrayList<Graphical> toClone2 = (ArrayList<Graphical>) gBoxClone.getGraphicModel().getAllObjectNoInternals().clone();
        //the following variables are needed for creating the connections
        ArrayList<GConnection> conns = new ArrayList<GConnection>();
        Map<Graphical,Graphical> oldToClone = new HashMap<Graphical,Graphical>();

        for (Graphical g : toClone) {//loops through all the nodes
            if(GConnection.class.isAssignableFrom(g.getClass())){
                conns.add((GConnection)g);
            }else if(GBoxNode.class.isAssignableFrom(g.getClass())){
            //node per node klonen
                GBoxNode gbn = (GBoxNode)g;
                BoxNode bn = (BoxNode)sync.get(gbn);
                Location loc = new AbsoluteLocation((int)gbn.getLocation().getX(), (int)gbn.getLocation().getY());
                PrototypeFactory pf = new PrototypeFactory(loc, gbn, bn);
                GBoxNode gnClone = pf.getGClone();
                BoxNode nClone = pf.getCClone();
                replace.add(gnClone);

                GResizeHandle gr = new GResizeHandle(gnClone, 50);
                gnClone.setResizer(gr);
                replace.add(gr);
                
                sync.put(gnClone, nClone);
                nodes.put(gbn.getName(), nClone);

                //loop through the transmitters and add them
                for(Graphical gc : gnClone.getTransmitterObjects()){
                    for(Graphical go : gbn.getTransmitterObjects()){
                        if(go.getClass().equals(gc.getClass())){
                            if(GDispatcherPort.class.isAssignableFrom(go.getClass())
                             || GInputPort.class.isAssignableFrom(go.getClass())){
                                if(go.getName().equals(gc.getName())){
                                    oldToClone.put(go, gc);
                                }
                            }else if((GTrigger.class.isAssignableFrom(go.getClass())
                              || GOutput.class.isAssignableFrom(go.getClass()))
                                    && (((AbstractHandle)go).getBase().getName().equals(((AbstractHandle)gc).getBase().getName()))){
                                        oldToClone.put(((AbstractHandle)go).getBase(), gc);
                            }
                        }
                    }
                }
                comms.addAll(pf.getCommands());
                comms.add(new ContainerCommand(new RedrawCommand(gnClone),gBoxClone.getGraphicModel()));

            }else if (GTransmitterNode.class.isAssignableFrom(g.getClass())) {
                GTransmitterNode gt = (GTransmitterNode)g;
                if (gt.getBase().equals(prototypeGBoxNode)){
                    GTransmitterNode gtClone = new GTransmitterNode(gBoxClone,gt.isLeft());
                    replaceTransmitters.add(gtClone);
                    transmitterNodes.put(gt.getName(), gtClone);
                    if(gt.getType().equals(Constants.getValueTransmitterOutString())){
                         ValueTransmitterNode n = (ValueTransmitterNode)sync.get(gt);
                         cBoxClone.putOutPort(gt.getName(), n.getInPort().getValueClass());
                    }else if(gt.getType().equals(Constants.getValueTransmitterInString())){
                         ValueTransmitterNode n = (ValueTransmitterNode)sync.get(gt);
                         cBoxClone.putOutPort(gt.getName(),n.getInPort().getValueClass());
                    }else if(gt.getType().equals(Constants.getTriggerTransmitterOutString())){
                         cBoxClone.putDispatcherPort(gt.getName());
                    }else if(gt.getType().equals(Constants.getTriggerTransmitterInString())){
                         cBoxClone.putTriggerPort(gt.getName());
                    }
                    for (Graphical g3 : toClone) {
                        if (GTriggerPort.class.isAssignableFrom(g3.getClass())) {
                            GTriggerPort gt3 = (GTriggerPort) g3;
                            if (gt3.getBase().equals(gt)) {
                                GTriggerPort aclone = (GTriggerPort) gt3.clone();
                                GTrigger c = new GTrigger(aclone, 5);
                                aclone.attach(gtClone);
                                oldToClone.put(gt3, c);
                                replaceTransmitters.add(aclone);
                                replaceTransmitters.add(c);
                            }
                        }else if (GDispatcherPort.class.isAssignableFrom(g3.getClass())) {
                            GDispatcherPort gt3 = (GDispatcherPort) g3;
                            if (gt3.getBase().equals(gt)) {
                                GDispatcherPort aclone = (GDispatcherPort) gt3.clone();
                                GDispatcher d = new GDispatcher(aclone);
                                aclone.attach(gtClone);
                                oldToClone.put(gt3, aclone);
                                replaceTransmitters.add(aclone);
                                replaceTransmitters.add(d);
                            }
                        }else if (GInputPort.class.isAssignableFrom(g3.getClass())) {
                            GInputPort gi3 = (GInputPort) g3;
                            if (gi3.getBase().equals(gt)) {
                                GInputPort aclone = (GInputPort) gi3.clone();
                                aclone.attach(gtClone);
                                GInput gi = new GInput(aclone);
                                oldToClone.put(gi3, aclone);
                                replaceTransmitters.add(aclone);
                                replaceTransmitters.add(gi);
                            }
                        }else if (GOutputPort.class.isAssignableFrom(g3.getClass())) {
                            GOutputPort go3 = (GOutputPort) g3;
                            if (go3.getBase().equals(gt)) {
                                GOutputPort aclone = (GOutputPort) go3.clone();
                                GOutput t = new GOutput(aclone);
                                aclone.attach(gtClone);
                                oldToClone.put(go3, t);
                                replaceTransmitters.add(aclone);
                                replaceTransmitters.add(t);
                            }
                        }
                    }
                }
            }else if (GNode.class.isAssignableFrom(g.getClass())) {
                GNode gn = (GNode) g;
                Node n = sync.get(gn);
                Node nClone = null;
                if (Store.class.isAssignableFrom(n.getClass())) {
                    Store st = (Store) n;
                    OutPort p = (OutPort) st.getPort("value");
                    nClone = new Store(p.getValue());
                } else {
                    Class c = n.getClass();
                    try {
                        Object o = c.newInstance();
                        nClone = Node.class.cast(o);
                    } catch (InstantiationException ex) {
                        Logger.getLogger(PrototypeFactory.class.getName()).log(Level.SEVERE, null, ex);
                    } catch (IllegalAccessException ex) {
                        Logger.getLogger(PrototypeFactory.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                GNode gnClone = (GNode) gn.clone();
                gnClone.setBoxNode(gBoxClone);
                gnClone.setLocations(new AbsoluteLocation(gBoxClone.getX() + (g.getGraphLocation().getX() - locationold.getX()), gBoxClone.getY() + (g.getGraphLocation().getY() - locationold.getY())));
                replace.add(gnClone);
                toClone2.remove(gn);
                for (Graphical g2 : toClone) {//loops through all the attachments of the current node
                    if (Attachment.class.isAssignableFrom(g2.getClass())) {
                        Attachment gh = (Attachment) g2;
                        if (gh.getBase().equals(g)) {
                            if (GResizeHandle.class.isAssignableFrom(g2.getClass())) {
                                GResizeHandle rh = (GResizeHandle) g2;
                                GResizeHandle rclone = (GResizeHandle) rh.clone();
                                rclone.setLocation(gnClone.getLowerRight());
                                rclone.setLocations(gnClone.getLowerRight());
                                rclone.setBase(gnClone);
                                gnClone.setResizer(rclone);
                                replace.add(rclone);
                                toClone2.remove(g2);
                            } else if (AbstractOutPort.class.isAssignableFrom(g2.getClass())) {
                                RightRectangleHandle rr = (RightRectangleHandle) g2;
                                RightRectangleHandle rclone = (RightRectangleHandle) rr.clone();
                                rclone.adjustLocations(gnClone, gnClone.getUpperRight());
                                rclone.setBase(gnClone);
                                replace.add(rclone);
                                toClone2.remove(g2);
                                for (Graphical g3 : toClone) {
                                    if (AbstractHandle.class.isAssignableFrom(g3.getClass())) {
                                        AbstractHandle gh3 = (AbstractHandle) g3;
                                        if (gh3.getBase().equals(rr)) {
                                            AbstractHandle aclone = (AbstractHandle) gh3.clone();
                                            aclone.setBase(rclone);
                                            aclone.move(rclone);
                                            replace.add(aclone);
                                            if(GDispatcherPort.class.isAssignableFrom(g2.getClass())){
                                                oldToClone.put(rr,rclone);
                                            }else{//GOutput
                                                oldToClone.put(rr, aclone);
                                            }
                                            toClone2.remove(g3);
                                        }
                                    }
                                }
                            } else if (AbstractInPort.class.isAssignableFrom(g2.getClass())) {
                                AbstractInPort lr = (AbstractInPort) g2;
                                AbstractInPort lclone = (AbstractInPort) lr.clone();
                                lclone.adjustLocations(gnClone, gnClone.getUpperLeft());
                                lclone.setBase(gnClone);
                                replace.add(lclone);
                                toClone2.remove(g2);
                                for (Graphical g3 : toClone) {
                                    if (AbstractHandle.class.isAssignableFrom(g3.getClass())) {
                                        AbstractHandle gh3 = (AbstractHandle) g3;
                                        if (gh3.getBase().equals(lr)) {
                                            AbstractHandle aclone = (AbstractHandle) gh3.clone();
                                            aclone.setBase(lclone);
                                            aclone.move(lclone);
                                            replace.add(aclone);
                                            if(GInputPort.class.isAssignableFrom(g2.getClass())){
                                                oldToClone.put(lr, lclone);
                                            }else{
                                                oldToClone.put(lr, aclone);
                                            }
                                            toClone2.remove(g3);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                sync.put(gnClone, nClone);
                nodes.put(gn.getName(), nClone);
            }
        }

        gBoxClone.setInternalObjects(replace);
        gBoxClone.setTransmitterObjects(replaceTransmitters);

        for (String s : nodes.keySet()) {
            cBoxClone.putNode(s, nodes.get(s));
        }

        for(String s : transmitterNodes.keySet()){
            sync.put((Node)cBoxClone.getTransmitterNode(s), transmitterNodes.get(s));
        }

        for(GConnection con : conns){
            AddConnectionCommand ac = null;
            if(GHandleConnection.class.isAssignableFrom(con.getClass())){
                GHandleConnection ghc = (GHandleConnection)con;
                ac = new AddConnectionCommand((GOutput)oldToClone.get(ghc.getOutput().getBase()), (GInputPort)oldToClone.get(ghc.getInput()));
            }else{
                GTriggerConnection gtc = (GTriggerConnection)con;
                ac = new AddConnectionCommand((GDispatcherPort)oldToClone.get(gtc.getDispatcher()),(GTrigger)oldToClone.get(gtc.getTrigger().getBase()));
            }
            comms.add(new ContainerCommand(ac,gBoxClone.getGraphicModel()));
        }
    }

    /**
     * @return the gClone
     */
    public GBoxNode getGClone() {
        return gBoxClone;
    }

    /**
     * @param gClone the gClone to set
     */
    public void setGClone(GBoxNode gClone) {
        this.gBoxClone = gClone;
    }

    public ArrayList<BasicCommand> getCommands(){
        return comms;
    }

    /**
     * @return the cClone
     */
    public BoxNode getCClone() {
        return cBoxClone;
    }


    /**
     * @return the nodes
     */
    public HashMap<String, Node> getNodes() {
        return nodes;
    }
}